package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.entity.DishFlavor;
import com.itheima.reggie.mapper.DishMapper;
import com.itheima.reggie.service.DishFlavorService;
import com.itheima.reggie.service.DishService;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class DishServiceimpl extends ServiceImpl<DishMapper, Dish> implements DishService{

    @Autowired
    private DishFlavorService dishFlavorService;

    @Resource
    private RedisTemplate<String,Object> redisTemplatess;

    @Override
    public IPage<Dish> sleectPage(Integer pageNumber, Integer pageSize,String name) {
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(null!=name,Dish::getName,name);
        Page<Dish> pageP = new Page<>(pageNumber,pageSize);
        Page<Dish> page = this.page(pageP,queryWrapper);
        return page;
    }

    /**
     * 批量删除
     */
    @Override
    public boolean deleteList(List<Long> ids) {
        String redisKey = null;
        Iterator<Long> iterator = ids.iterator();
        while (iterator.hasNext()==true){
             Long next = iterator.next();
             Dish byId = this.getById(next);
            redisKey = byId.getCategoryId()+"_"+byId.getStatus();
//            if (redisKey)
             redisTemplatess.delete(redisKey);
        }


        boolean b = this.removeByIds(ids);
        return b;
    }

    /**
     * 停售
     */
    @Override
    public boolean updateStatusx(Integer state, List<Long> ids) {

        UpdateWrapper<Dish> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda()
                .in(Dish::getId, ids)
                .set(Dish::getStatus,state);

        boolean res= this.update(updateWrapper);
        return res;
    }
    /**
     * 新增菜品
     */
    @Override
    public boolean addDish(DishDto dish) {
       //保存菜品基本信息到菜品表中
        this.save(dish);

        //拿到菜品ID
        Long id = dish.getId();
        //开始菜品口味集合对象
         List<DishFlavor> flavors = dish.getFlavors();
         //需要把菜品ID设置到每个口味表里面的对象里去
        boolean b = dishFlavorService.saveBatch(flavors);


        return b;
    }

    /**
     * 查询菜品集合
     * @param
     */
    @Override
    public List<Dish> selectList(Long categoryId,Integer status) {
       //先去Redis当中去查，拼写出不同的key，因为我们是分类存储的吗
        List<Dish> RedisList = null;
        //动态拼写出Redis不同的key
        String keyValue = "dish_"+categoryId+"_"+status;

        //查询Redis数据库中的数据  如果Redis当中的数据不是空的那么就直接返回给前端
        RedisList = (List<Dish>) redisTemplatess.opsForValue().get(keyValue);
        if (RedisList!=null){
            return RedisList;
        }
        //如果Redis当中没有数据那么就走mysql的数据
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(categoryId!=null,Dish::getCategoryId,categoryId)
                .eq(status!=null,Dish::getStatus,status);
        List<Dish> list = this.list(queryWrapper);
        List<Dish> collect = list.stream().collect(Collectors.toList());
        //然后将查出的数据存放在Redis当中
        redisTemplatess.opsForValue().set(keyValue,collect,60,TimeUnit.MINUTES);

        return list;
    }

    @Override
    public Dish getID(Long id) {
        String keyValue = "dish_"+id;
        //首先先看Redis当中的数据是否为空
        Dish dish = (Dish) redisTemplatess.opsForValue().get(keyValue);
        if (dish!=null){
            return dish;
        }
        //如果为空访问数据库
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Dish::getCategoryId,id);
        Dish one = this.getOne(queryWrapper);
        //如果说数据库查出来的数据也是空那么给此key设置在Redis当中一个空值 但是过期时间最多只有五分钟
        if (one==null){
            redisTemplatess.opsForValue().set(keyValue,null,5,TimeUnit.MINUTES);
        }else {
            //数据库查出来的数据放入Redis当中
            redisTemplatess.opsForValue().set(keyValue,one,90,TimeUnit.MINUTES);
        }
        return one;
    }

    /**
     * 缓存击穿问题互斥锁的解决方案
     * @param
     * @return
     */
    @Override
    public  Dish  selectID(Long selectxD) {
        //先设置key
        String key  = "CACHE_SHOP_KEY"+selectxD;
        System.out.println("当前线程是"+Thread.currentThread().getName());
        //先从Redis当中查询缓存的菜品信息
        Dish dish = (Dish) redisTemplatess.opsForValue().get(key);
        //判断是否为空
        if (dish!=null){

            return dish;
        }
        //否则就是空 实现缓存重建获取互斥锁
        String lockKey = "lock:dish:"+selectxD;
        Dish dish1 = null;
        boolean islocak = tryLock(lockKey);
        //判断是否获取成功
        Dish byId = null;
        try {
            if (!islocak){
                //失败则休眠并重试
                Thread.sleep(50);
                //递归本次方法

                return selectID(selectxD);
            }
            //成功则根据ID查询数据库
            byId = this.getById(selectxD);
            //不存在则返回错误
            if (byId==null){
                //缓存击穿问题
                //将空值写入Redis
                redisTemplatess.opsForValue().set(key,"",5,TimeUnit.MINUTES);
                return null;
            }
            //存在则写入Redis
            redisTemplatess.opsForValue().set(key,byId,50,TimeUnit.MINUTES);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放互斥锁
            unlock(lockKey);
        }
        return byId;
    }

    //编写获得锁的方法
    private boolean tryLock(String key){
        /**
         * setIfAbsent:::
         * 如果为空就set值，并返回1
         * 如果存在(不为空)不进行操作，并返回0
         * 1.如果键不存在则新增,存在则不改变已经有的值。
         *   2.存在返回 false，不存在返回 true。
         *   3.boolean lock = redisTemplate.opsForValue().setIfAbsent("testKey","123");
         *         一开始是不存在key “testKey” 的，所以lock 为 true；但是后面创建并添加了值“123”。
         *      redisTemplate.opsForValue().get("testKey")；
         *         是有值的，为“123”。
         *      lock = redisTemplate.opsForValue().setIfAbsent("testKey","321");
         *         已存在key “testKey”，所以无法赋值，lock 为 false；
         *     redisTemplate.opsForValue().get("testKey")；
         *         值没变，还是“123”。
         */
        boolean flage = redisTemplatess.opsForValue().setIfAbsent(key,"1",10, TimeUnit.SECONDS);
        return BooleanUtils.isTrue(flage);
    }

    //编写释放锁的方法
    private void unlock(String key){
        redisTemplatess.delete(key);
    }





}
